package net.stogi.unicore.beyond.ui.client.taskeditor.taskpreview;

import net.stogi.common.gwt.dispatch.client.core.ActionManager;
import net.stogi.common.gwt.frame.client.core.EventBus;
import net.stogi.common.gwt.frame.client.mvp.AbstractPresenter;
import net.stogi.common.mvp.core.behavior.BehaviorWithValue;
import net.stogi.unicore.beyond.api.exception.InputDefinitionAlreadyExistsException;
import net.stogi.unicore.beyond.api.exception.InputGroupAlreadyExistsException;
import net.stogi.unicore.beyond.api.exception.OutputDefinitionAlreadyExistsException;
import net.stogi.unicore.beyond.api.exception.OutputGroupAlreadyExistsException;
import net.stogi.unicore.beyond.api.model.InputDefinition;
import net.stogi.unicore.beyond.api.model.InputGroup;
import net.stogi.unicore.beyond.api.model.InputType;
import net.stogi.unicore.beyond.api.model.OutputDefinition;
import net.stogi.unicore.beyond.api.model.OutputGroup;
import net.stogi.unicore.beyond.api.model.OutputType;
import net.stogi.unicore.beyond.api.model.TaskDefinition;
import net.stogi.unicore.beyond.ui.client.taskeditor.taskoutline.InputDefinitionMovedEvent;
import net.stogi.unicore.beyond.ui.client.taskeditor.taskoutline.InputDefinitionMovedHandler;
import net.stogi.unicore.beyond.ui.client.taskeditor.taskoutline.InputDefinitionRemovedEvent;
import net.stogi.unicore.beyond.ui.client.taskeditor.taskoutline.InputDefinitionRemovedHandler;
import net.stogi.unicore.beyond.ui.client.taskeditor.taskoutline.InputGroupMovedEvent;
import net.stogi.unicore.beyond.ui.client.taskeditor.taskoutline.InputGroupMovedHandler;
import net.stogi.unicore.beyond.ui.client.taskeditor.taskoutline.InputGroupRemovedEvent;
import net.stogi.unicore.beyond.ui.client.taskeditor.taskoutline.InputGroupRemovedHandler;
import net.stogi.unicore.beyond.ui.client.taskeditor.taskoutline.OutputDefinitionAddedEvent;
import net.stogi.unicore.beyond.ui.client.taskeditor.taskoutline.OutputDefinitionMovedEvent;
import net.stogi.unicore.beyond.ui.client.taskeditor.taskoutline.OutputDefinitionMovedHandler;
import net.stogi.unicore.beyond.ui.client.taskeditor.taskoutline.OutputDefinitionRemovedEvent;
import net.stogi.unicore.beyond.ui.client.taskeditor.taskoutline.OutputDefinitionRemovedHandler;
import net.stogi.unicore.beyond.ui.client.taskeditor.taskoutline.OutputGroupMovedEvent;
import net.stogi.unicore.beyond.ui.client.taskeditor.taskoutline.OutputGroupMovedHandler;
import net.stogi.unicore.beyond.ui.client.taskeditor.taskoutline.OutputGroupRemovedEvent;
import net.stogi.unicore.beyond.ui.client.taskeditor.taskoutline.OutputGroupRemovedHandler;
import net.stogi.unicore.beyond.ui.client.taskeditor.taskoutline.TaskDefinitionEditedEvent;
import net.stogi.unicore.beyond.ui.client.taskeditor.taskoutline.TaskDefinitionEditedHandler;
import net.stogi.unicore.beyond.ui.shared.taskeditor.action.model.SaveTaskDefinitionAction;
import net.stogi.unicore.beyond.ui.shared.taskeditor.action.model.SaveTaskDefinitionResult;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;

class TaskPreviewPresenter extends AbstractPresenter {

	private TaskDefinition taskDefinition;

	private TaskPreviewView taskPreviewView;

	public TaskPreviewPresenter(TaskPreviewView taskPreviewView, ActionManager actionManager, EventBus eventBus) {
		super(actionManager, eventBus);
		this.taskPreviewView = taskPreviewView;
		initBehavior();
		initHandlers();
	}

	@Override
	protected void initBehavior() {
		taskPreviewView.setCloseBehavior(new BehaviorWithValue<Boolean>() {

			@Override
			public void behave(Boolean value) {
				if (value != null) {
					if (value) {
						// TODO: Save.
						internalCollectTaskDefinition(taskDefinition);
						executeSaveTaskDefinition(taskDefinition);
					}
					else {
						// TODO: Do not save.
					}
					taskDefinition = null;
					taskPreviewView.disappear();

					eventBus.fireEvent(new TaskPreviewClosedEvent());
				}
			}
		});
	}

	@Override
	protected void initHandlers() {
		// Open task preview.
		eventBus.addHandler(OpenTaskPreviewEvent.TYPE, new OpenTaskPreviewHandler() {

			@Override
			public void onOpenTaskPreview(OpenTaskPreviewEvent event) {
				if (event.getTaskDefinition() != null) {
					taskDefinition = event.getTaskDefinition();
				}
				else {
					taskDefinition = new TaskDefinition();
				}
				internalDisplayTaskDefinition(taskDefinition);
				taskPreviewView.appear();

				eventBus.fireEvent(new TaskPreviewOpenedEvent(taskDefinition));
			}
		});
		// Add new input group to task preview and model.
		eventBus.addHandler(AddInputGroupEvent.TYPE, new AddInputGroupHandler() {

			@Override
			public void onAddInputGroup(AddInputGroupEvent event) {
				if (taskDefinition != null) {
					try {
						String name = internalGenerateNewInputGroupName(taskDefinition);
						InputGroup inputGroup = taskDefinition.addInputGroup(name);

						taskPreviewView.addInputGroup(inputGroup.getName());
						eventBus.fireEvent(new InputGroupAddedEvent(inputGroup));
					}
					catch (InputGroupAlreadyExistsException e) {
						// TODO: Handle exception in proper way for user.
						GWT.log("TaskPreviewPresenter.onAddInputGroup(): problem while adding new group", e);
					}
				}
			}
		});
		// Add new input definition to task preview.
		eventBus.addHandler(AddInputDefinitionEvent.TYPE, new AddInputDefinitionHandler() {

			@Override
			public void onAddInputDefinition(AddInputDefinitionEvent event) {
				InputType inputType = event.getInputType();

				GWT.log("TaskPreviewPresenter.onAddInputDefinition(): input type [ inputType.name = '" + inputType.getName() + "' ]");

				// User sees input panel.
				if (taskPreviewView.isInputPanelVisible()) {
					// Exists selected input panel.
					int inputGroupIndex = taskPreviewView.collectSelectedInputGroup();
					if (inputGroupIndex > -1) {
						// Get selected group.
						InputGroup inputGroup = taskDefinition.getInputGroup(inputGroupIndex);
						// Generate definition name.
						String definitionName = internalGenerateNewInputDefinitionName(taskDefinition);

						try {
							InputDefinition inputDefinition = inputGroup.addInputDefinition(definitionName, inputType);
							// Update view.
							taskPreviewView.addInputDefinition(inputDefinition, inputGroupIndex);
							// Inform rest of modules.
							eventBus.fireEvent(new InputDefinitionAddedEvent(inputDefinition, inputGroup));
						}
						catch (InputDefinitionAlreadyExistsException e) {
							// Name is generated. This should not happened.
							GWT.log("TaskPreviewPresenter.onAddInputDefinition(): problem while adding new definition", e);
						}
					}
				}
			}
		});
		// Input definition was moved. Refresh preview.
		eventBus.addHandler(InputDefinitionMovedEvent.TYPE, new InputDefinitionMovedHandler() {

			@Override
			public void onInputDefinitionMoved(InputDefinitionMovedEvent event) {
				internalDisplayTaskDefinition(taskDefinition);
				taskPreviewView.showInputSection();
				taskPreviewView.selectInputGroup(event.getInputGroup().getIndex());
			}
		});
		// Input definition was removed. Refresh preview.
		eventBus.addHandler(InputDefinitionRemovedEvent.TYPE, new InputDefinitionRemovedHandler() {

			@Override
			public void onInputDefinitionRemoved(InputDefinitionRemovedEvent event) {
				internalDisplayTaskDefinition(taskDefinition);
				taskPreviewView.showInputSection();
				taskPreviewView.selectInputGroup(event.getInputGroup().getIndex());
			}
		});
		// Input group was moved. Refresh preview.
		eventBus.addHandler(InputGroupMovedEvent.TYPE, new InputGroupMovedHandler() {

			@Override
			public void onInputGroupMoved(InputGroupMovedEvent event) {
				internalDisplayTaskDefinition(taskDefinition);
				taskPreviewView.showInputSection();
			}
		});
		// Input group was removed. Refresh preview.
		eventBus.addHandler(InputGroupRemovedEvent.TYPE, new InputGroupRemovedHandler() {

			@Override
			public void onInputGroupRemoved(InputGroupRemovedEvent event) {
				internalDisplayTaskDefinition(taskDefinition);
				taskPreviewView.showInputSection();
			}
		});
		// Add new output group to task preview and model.
		eventBus.addHandler(AddOutputGroupEvent.TYPE, new AddOutputGroupHandler() {

			@Override
			public void onAddOutputGroup(AddOutputGroupEvent event) {
				if (taskDefinition != null) {
					try {
						String name = internalGenerateNewOutputGroupName(taskDefinition);
						OutputGroup outputGroup = taskDefinition.addOutputGroup(name);

						taskPreviewView.addOutputGroup(outputGroup.getName());
						eventBus.fireEvent(new OutputGroupAddedEvent(outputGroup));
					}
					catch (OutputGroupAlreadyExistsException e) {
						// TODO: Handle exception in proper way for user.
						GWT.log("TaskPreviewPresenter.onAddOutputGroup(): problem while adding new group", e);
					}
				}
			}
		});
		// Add new output definition to task preview.
		eventBus.addHandler(AddOutputDefinitionEvent.TYPE, new AddOutputDefinitionHandler() {

			@Override
			public void onAddOutputDefinition(AddOutputDefinitionEvent event) {
				OutputType outputType = event.getOutputType();

				GWT.log("TaskPreviewPresenter.onAddOutputDefinition(): output type [ outputType.name = '" + outputType.getName() + "' ]");

				// User sees output panel.
				if (taskPreviewView.isOutputPanelVisible()) {
					// Exists selected output panel.
					int outputGroupIndex = taskPreviewView.collectSelectedOutputGroup();
					if (outputGroupIndex > -1) {
						// Get selected group.
						OutputGroup outputGroup = taskDefinition.getOutputGroup(outputGroupIndex);
						// Generate definition name.
						String definitionName = internalGenerateNewOutputDefinitionName(taskDefinition);

						try {
							OutputDefinition outputDefinition = outputGroup.addOutputDefinition(definitionName, outputType);
							// Update view.
							taskPreviewView.addOutputDefinition(outputDefinition, outputGroupIndex);
							// Inform rest of modules.
							eventBus.fireEvent(new OutputDefinitionAddedEvent(outputDefinition, outputGroup));
						}
						catch (OutputDefinitionAlreadyExistsException e) {
							// Name is generated. This should not happened.
							// TODO: Handle exception in proper way for user.
							GWT.log("TaskPreviewPresenter.onAddOutputDefinition(): problem while adding new definition", e);
						}
					}
				}
			}
		});
		// Output definition was moved. Refresh preview.
		eventBus.addHandler(OutputDefinitionMovedEvent.TYPE, new OutputDefinitionMovedHandler() {

			@Override
			public void onOutputDefinitionMoved(OutputDefinitionMovedEvent event) {
				internalDisplayTaskDefinition(taskDefinition);
				taskPreviewView.showOutputSection();
				taskPreviewView.selectOutputGroup(event.getOutputGroup().getIndex());
			}
		});
		// Output definition was removed. Refresh preview.
		eventBus.addHandler(OutputDefinitionRemovedEvent.TYPE, new OutputDefinitionRemovedHandler() {

			@Override
			public void onOutputDefinitionRemoved(OutputDefinitionRemovedEvent event) {
				internalDisplayTaskDefinition(taskDefinition);
				taskPreviewView.showOutputSection();
				taskPreviewView.selectOutputGroup(event.getOutputGroup().getIndex());
			}
		});
		// Output group was moved. Refresh preview.
		eventBus.addHandler(OutputGroupMovedEvent.TYPE, new OutputGroupMovedHandler() {

			@Override
			public void onOutputGroupMoved(OutputGroupMovedEvent event) {
				internalDisplayTaskDefinition(taskDefinition);
				taskPreviewView.showOutputSection();
			}
		});
		// Output group was removed. Refresh preview.
		eventBus.addHandler(OutputGroupRemovedEvent.TYPE, new OutputGroupRemovedHandler() {

			@Override
			public void onOutputGroupRemoved(OutputGroupRemovedEvent event) {
				internalDisplayTaskDefinition(taskDefinition);
				taskPreviewView.showOutputSection();
			}
		});
		// Task definition was edited.
		eventBus.addHandler(TaskDefinitionEditedEvent.TYPE, new TaskDefinitionEditedHandler() {

			@Override
			public void onTaskDefinitionEdited(TaskDefinitionEditedEvent event) {
				internalDisplayTaskDefinition(taskDefinition);
			}
		});
	}

	private void executeSaveTaskDefinition(TaskDefinition taskDefinition) {
		actionManager.execute(new SaveTaskDefinitionAction(taskDefinition), new AsyncCallback<SaveTaskDefinitionResult>() {

			@Override
			public void onFailure(Throwable caught) {
			// TODO: Implement me.
			}

			@Override
			public void onSuccess(SaveTaskDefinitionResult result) {
			// TODO: Implement me.
			}
		});
	}

	private void internalDisplayTaskDefinition(TaskDefinition taskDefinition) {
		taskPreviewView.clear();
		// Task fields.
		taskPreviewView.displayApplicationName(taskDefinition.getApplicationName());
		taskPreviewView.displayApplicationVersion(taskDefinition.getApplicationVersion());
		taskPreviewView.displayApplicationDescription(taskDefinition.getApplicationDescription());
		// Input fields.
		for (InputGroup inputGroup : taskDefinition.getInputGroups()) {
			taskPreviewView.addInputGroup(inputGroup.getName());
			GWT.log("internalDisplayTaskDefinition(): Input group added: [ inputGroup.name = '" + inputGroup.getName() + "' ]");
			GWT.log("internalDisplayTaskDefinition(): Input definition size: [ inputGroup.inputDefinitions.size = '"
					+ inputGroup.getInputDefinitions().size() + "' ]");
			for (InputDefinition inputDefinition : inputGroup.getInputDefinitions()) {
				taskPreviewView.addInputDefinition(inputDefinition, inputGroup.getIndex());
				GWT.log("internalDisplayTaskDefinition(): Input definition added: [ inputGroup.name = '" + inputGroup.getName() + "' ]");
			}
		}
		// Output fields.
		for (OutputGroup outputGroup : taskDefinition.getOutputGroups()) {
			taskPreviewView.addOutputGroup(outputGroup.getName());
			for (OutputDefinition outputDefinition : outputGroup.getOutputDefinitions()) {
				taskPreviewView.addOutputDefinition(outputDefinition, outputGroup.getIndex());
			}
		}
	}

	private void internalCollectTaskDefinition(TaskDefinition taskDefinition) {
		// Task fields.
		taskDefinition.setApplicationName(taskPreviewView.collectApplicationName());
		taskDefinition.setApplicationVersion(taskPreviewView.collectApplicationVersion());
		taskDefinition.setApplicationDescription(taskPreviewView.collectApplicationDescription());
	}

	private String internalGenerateNewInputDefinitionName(TaskDefinition taskDefinition) {
		assert taskDefinition != null;
		String name = "Input";
		int numberOfInputs = 0;
		for (InputGroup inputGroup : taskDefinition.getInputGroups()) {
			numberOfInputs += inputGroup.getInputDefinitions().size();
		}
		return name + numberOfInputs;
	}

	private String internalGenerateNewInputGroupName(TaskDefinition taskDefinition) {
		assert taskDefinition != null;
		return "InputGroup" + taskDefinition.getInputGroups().size();
	}

	private String internalGenerateNewOutputDefinitionName(TaskDefinition taskDefinition) {
		assert taskDefinition != null;
		String name = "Output";
		int numberOfOutputs = 0;
		for (OutputGroup outputGroup : taskDefinition.getOutputGroups()) {
			numberOfOutputs += outputGroup.getOutputDefinitions().size();
		}
		return name + numberOfOutputs;
	}

	private String internalGenerateNewOutputGroupName(TaskDefinition taskDefinition) {
		assert taskDefinition != null;
		return "OutputGroup" + taskDefinition.getOutputGroups().size();
	}
}
